﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using MovieRental.GeneralDataExceptions;
using MovieRental.DataInterfaces;
using MovieRental.SqlRepositoryLibrary.Converters;

namespace MovieRental.SqlRepositoryLibrary
{
    public abstract class SqlRepository<TEntity, TKey, TConverter> : IRepository<TEntity, TKey> 
        where TConverter : ISqlEntityConverter<TEntity, TKey>, new()
    {
        protected string _connectionString;

        protected ISqlEntityConverter<TEntity, TKey> Converter { private set; get; }

        public SqlConnection ProvideConnection()
        {
            SqlConnection connection = new SqlConnection(_connectionString);
            try { connection.Open(); }
            catch (Exception ex)
            {
                throw new DbConnectionException("Can't connect to the database.", ex);
            }
            return connection;
        }

        public SqlRepository(string connectionString)
        {
            Converter = new TConverter();
            _connectionString = connectionString;
        }

        public virtual TEntity Load(TKey id)
        {
            using (var connection = ProvideConnection())
            {
                SqlCommand command = Converter.GetReadCommand(id);
                command.Connection = connection;
                try
                {
                    SqlDataReader reader = command.ExecuteReader();
                    if (!reader.HasRows)
                        throw new ItemNotFoundException(id.ToString(), null);
                    reader.Read();
                    var result = Converter.ConvertFromDataReader(reader);
                    reader.Close();
                    return result;
                }
                catch (ItemNotFoundException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new DbConnectionException("Can't load object from the db.", ex);
                }
            }
        }

        public bool Contains(TKey id)
        {
            try { Load(id); }
            catch (ItemNotFoundException)
            {
                return false;
            }
            return true;
        }

        protected abstract bool Contains(TEntity entity);


        public virtual void Save(TEntity entity)
        {
            using (var connection = ProvideConnection())
            {
                SqlCommand command = Contains(entity) ? Converter.GetUpdateCommand(entity) : Converter.GetInsertCommand(entity);
                command.Connection = connection;
                try
                {
                    command.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    throw new DbConnectionException("Can't save object in the db.", ex);
                }
            }
        }

        public abstract void Delete(TEntity entity);

        public virtual void Delete(TKey id)
        {
            using (var connection = ProvideConnection())
            {
                SqlCommand command = Converter.GetDeleteCommand(id);
                command.Connection = connection;
                try
                {
                    int affected = command.ExecuteNonQuery();
                    if (affected == 0)
                        throw new ItemNotFoundException(id.ToString(), null);
                }
                catch (Exception ex)
                {
                    throw new DbConnectionException("Can't delete object from the db.", ex);
                }
            }
        }

        protected IEnumerable<TEntity> GetEnumerableFromReaderCommand(SqlCommand command)
        {
            SqlDataReader reader = command.ExecuteReader();
            ICollection<TEntity> query = new List<TEntity>();
            while (reader.Read())
                query.Add(Converter.ConvertFromDataReader(reader));
            reader.Close();
            return query;
        }

        public virtual IEnumerable<TEntity> LoadAll()
        {
            using (var connection = ProvideConnection())
            {
                SqlCommand command = Converter.GetReadAllCommand();
                command.Connection = connection;
                try
                {
                    return GetEnumerableFromReaderCommand(command);
                }
                catch (Exception ex)
                {
                    throw new DbConnectionException("Can't connect to the db.", ex);
                }
            }
        }
    }
}
